#  /**
#  * Copyright (c) 2022 Beijing Jiaotong University
#  * PhotLab is licensed under [Open Source License].
#  * You can use this software according to the terms and conditions of the [Open Source License].
#  * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
#  *
#  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
#  * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
#  * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#  *
#  * See the [Open Source License] for more details.
#  */
#  /**
#  * Author: Zheng Wang
#  * Created: Jul. 5, 2022
#  * Supported by: National Key Research and Development Program of China
#  */
import logging

from phot_engine.logical_structure.dispatcher import Dispatcher
from phot_engine.globals.engine_instruction import EngineInstruction
from phot_engine.globals.singletion import Singleton
from phot_engine.globals.task_status import TaskStatus
from phot_engine.globals.engine_exception import EngineError
from phot_engine.globals.engine_exception import DispatcherError


@Singleton
class TaskManager:
    """
        This class is used to manage simulation task,and generate engine instruction to a dispatcher
    """

    def __init__(self):
        self.__task_dispatcher_objects = dict()

    def register_object(self, task_id, task_obj) -> bool:
        """
            register a task dispatcher object in simulation task manager
            if manager has a same name task id,delete the old one and use new object to upgrade
        :param task_id:
        :param task_obj:
        :return:
        """
        if self.is_task_exist(task_id):
            if self.__judge_task_status(task_id) == TaskStatus.TASK_ACCOMPLISHED:
                self.unregister_object(task_id)
                task_dispatcher_object = Dispatcher(simulation_task=task_obj,
                                                    instruction=EngineInstruction.INITIALIZE)
                self.__task_dispatcher_objects[task_id] = task_dispatcher_object
                return True
            else:
                logging.error("Attempt to add a duplicate dispatcher object,"
                              "when the older one hasn't finished calculation")
                raise EngineError("Attempt to add a duplicate dispatcher object,"
                                  "when the older one hasn't finished calculation")
        else:
            task_dispatcher_object = Dispatcher(simulation_task=task_obj,
                                                instruction=EngineInstruction.INITIALIZE)
            self.__task_dispatcher_objects[task_id] = task_dispatcher_object
            return True

    def unregister_object(self, task_id):
        """
            unregister a task object in the simulation task manager
        :param task_id:
        :return:
        """
        if self.is_task_exist(task_id):
            if self.__judge_task_status(task_id) == TaskStatus.TASK_RUNNING:
                self.cancel_dispatcher(task_id=task_id)
                del self.__task_dispatcher_objects[task_id]
                return True
            elif self.__judge_task_status(task_id) == TaskStatus.TASK_ACCOMPLISHED:
                del self.__task_dispatcher_objects[task_id]
                return True
            elif self.__judge_task_status(task_id) == (TaskStatus.TASK_DIED or TaskStatus.TASK_NO_RESPONDING):
                del self.__task_dispatcher_objects[task_id]
                return True
            elif self.__judge_task_status(task_id) == TaskStatus.TASK_STOPPED:
                del self.__task_dispatcher_objects[task_id]
                return True
            else:
                err_msg = "Attempt to delete an executing simulation task dispatcher.task_id:{},status:{}"\
                    .format(task_id,
                            self.__task_dispatcher_objects[task_id].get_task_status())
                logging.error(err_msg)
                raise EngineError(err_msg)
        else:
            raise DispatcherError("Simulation task fails to delete!!!")

    def get_object(self, task_id):
        """
            get task dispatcher object
        :param task_id:Dispatcher type
        :return:
        """
        if self.is_task_exist(task_id):
            return self.__task_dispatcher_objects[task_id]
        else:
            return None

    def is_task_exist(self, task_id):
        """
            判断任务是否存在
            Determine whether the task exists
        :param task_id:
        :return:
        """
        if task_id in self.__task_dispatcher_objects.keys():
            if self.__task_dispatcher_objects[task_id] is not None:
                return True
            else:
                return False
        else:
            return False

    def use_dispatcher(self, task_id):
        task_dispatcher = self.get_object(task_id=task_id)
        logging.debug("fetch task:{},dispatcher object:{},dispatcher object id:{}".
                      format(task_id, task_dispatcher, id(task_dispatcher)))

        if task_dispatcher is not None and isinstance(task_dispatcher, Dispatcher):
            task_dispatcher.alter_instruction(EngineInstruction.EXECUTE)
            task_dispatcher.execute_instruction()

        else:
            pass

    def pause_dispatcher(self, task_id):
        task_dispatcher = self.get_object(task_id=task_id)
        if task_dispatcher is not None and isinstance(task_dispatcher, Dispatcher):
            task_dispatcher.alter_instruction(EngineInstruction.HALT)
            task_dispatcher.execute_instruction()
        else:
            pass

    def recover_dispatcher(self, task_id):
        task_dispatcher = self.get_object(task_id=task_id)
        if task_dispatcher is not None and isinstance(task_dispatcher, Dispatcher):
            task_dispatcher.alter_instruction(EngineInstruction.RECOVER)
            task_dispatcher.execute_instruction()
            task_dispatcher.join()
            task_dispatcher.get_task().set_task_status(TaskStatus.TASK_ACCOMPLISHED)
        else:
            pass

    def cancel_dispatcher(self, task_id):
        task_dispatcher = self.get_object(task_id=task_id)
        if task_dispatcher is not None and isinstance(task_dispatcher, Dispatcher):
            task_dispatcher.alter_instruction(EngineInstruction.CANCEL)
            task_dispatcher.execute_instruction()
            task_dispatcher.close()
        else:
            raise DispatcherError("You shouldn't cancel a void task! task id:{}".format(task_id))

    def delete_dispatcher(self, task_id):
        self.unregister_object(task_id=task_id)

    def __judge_task_status(self, task_id):
        """
            judge task status
        :param task_id:
        :return:
        """
        task_dispatcher = self.get_object(task_id=task_id)
        if task_dispatcher is not None and isinstance(task_dispatcher, Dispatcher):
            return task_dispatcher.get_task_status()
        else:
            logging.error("dispatcher is none")
            return TaskStatus.TASK_DIED
